
// 常量
// 每次移动的距离，步长
var STEP = 20;

// 将容器进行分割，分割为 18*20行，10*20列。行高，列高均为 20 
var ROW_COUNT =18,
    COL_COUNT = 10;

// 创建每个模型的数据源
var MODELS = [
    // 第一个模型的数据源(L型 )
    // 伪数组
    {
        0: {
            row:2,
            col:0
        },

        1: {
            row:2,
            col:1
        },

        2: {
            row:2,
            col:2
        },

        3: {
            row:1,
            col:2
        },
    },
         // 第二个样式(凸)
         {
            0: {
                row: 1,
                col: 1
            },
            1: {
                row: 0,
                col: 0
            },
            2: {
                row: 1,
                col: 0
            },
            3: {
                row: 2,
                col: 0
            },
        },
        //  第三个样式(田)
        {
            0: {
                row: 1,
                col: 1
            },
            1: {
                row: 2,
                col: 1
            },
            2: {
                row: 1,
                col: 2
            },
            3: {
                row: 2,
                col: 2
            },
        },
        // 第四个样式(一)
        {
            0: {
                row: 0,
                col: 0
            },
            1: {
                row: 0,
                col: 1
            },
            2: {
                row: 0,
                col: 2
            },
            3: {
                row: 0,
                col: 3
            },
        },
        // 第五个样式(Z)
        {
            0: {
                row: 1,
                col: 1
            },
            1: {
                row: 1,
                col: 2
            },
            2: {
                row: 2,
                col: 2
            },
            3: {
                row: 2,
                col: 3
            },
        }  
    ]


// 变量
// 当前使用的模型
var currentModel = {
}
// 标记16宫格的位置
var currentX = 0,
    currentY = 0;

// 记录所有块元素的位置
// K = 行_列  : V = 块元素
var fixedBlocks = { 
}

// 定时器
var mInterval = null;


// 入口方法(函数)
function init() {
    createModel();
    onKeyDown();
   
}

// 根据模型的数据源来创建对应的块级元素
function createModel() {

    // 判断游戏是否已经结束了
    if (isGameOver()) {
        GameOver();
        return;
    }

    // 确定当前使用哪一个模型
    // 使用lodash提供的随机数方法
    currentModel = MODELS[_.random(0,MODELS.length-1)];

    // 重新初始化16宫格的位置
    currentX = 0,
    currentY = 0;

    // 生成对应数量的块元素
    for (var key in currentModel) {
        var divEle= document.createElement("div");
        divEle.className = "activity_model";
        document.getElementById("container").appendChild(divEle);   
        }    
        // 定位块元素位置
        locationBlocks();
        // 模型自动下落
        autoDown();
    }

// 根据数据源定位块元素的位置
function locationBlocks() {
    // 在我们定位我们块元素位置之前
    // 判断一些块元素的越界行为
    checkBound();

    // 1.拿到所有的块元素
    var eles = document.getElementsByClassName("activity_model");
    for (var i=0;i<eles.length;i++) {
        // 单个块元素
        var activityModelEle = eles[i];
        // 2.找到每个块元素对应的数据（行、列）
        var blockModel = currentModel[i];
        // 3.根据每个块元素对应的数据来指定块元素的位置
        // 每个块元素的位置由两个值确定： 1.16宫格所在的位置    2.块元素在16宫格中的位置
        activityModelEle.style.top =(currentY + blockModel.row)*STEP + "px";
        activityModelEle.style.left = (currentX + blockModel.col)*STEP + "px";
    }
    

}


// 监听用户的键盘事件
function onKeyDown() {
    document.onkeydown = function (event) {
        // console.log(event.keyCode);     //keyCode表示键盘编码--表示键盘上真实键的数字
        switch (event.keyCode) {
            case 37: 
            console.log("左");
            move(-1,0);
            break;
        
            case 38: 
            console.log("上");
            // move(0,-1);                 //修改
            rotate();
            break;

            case 39: 
            console.log("右");
            move(1,0);
            break;

            case 40: 
            console.log("下");
            move(0,1);
            break;

            case 32:
            console.log("空格");
            // move(0,-1);                 //修改
            rotate();
            break;

            case 65:
            console.log("A左");
            move(-1,0);
            break;

            case 87:
            console.log("W上");           //修改
            rotate();
            // move(0,-1);  
            break;

            case 68:
            console.log("D右");
            move(1,0);
            break;

            case 83:
            console.log("S下");
            move(0,1);
            break;
        }
        
    }
} 

// 移动
function move(x,y) {
    // 控制块元素移动
    // var activityModelEle=document.getElementsByClassName("activity._model")[0];
    // activityModelEle.style.top = parseInt(activityModelEle.style.top||0)+y*STEP+"px";
    // activityModelEle.style.left = parseInt(activityModelEle.style.left||0)+x*STEP+"px";

    // 上下左右触碰
    if (isMeet(currentX+x,currentY+y,currentModel)) {

        // 底部的触碰发生在移动16宫格的时候，并且此次移动是因为Y轴的变化而引起的
        if (y!==0) {
            // 模型之间底部发生触碰了
            fixedBottomModel();
        }
        return;
    }

    // 16宫格在动
    currentX+= x;
    currentY+=y;
    // 根据16宫格的位置来重新定位块元素
    locationBlocks();
}

// 旋转模型
function rotate() {
    // 算法
    // 旋转后的行 = 旋转前的列
    // 旋转后的列 = 3 - 旋转前的行

    // 克隆一下 currentModel
    var cloneCurrentModel = _.cloneDeep(currentModel);      //调用lodash方法
    // 此方法将当前的currentModel克隆出来一个新的cloneCurrentModel对象，并且新克隆出来的对象的数据的改变不会影响到原先的对线，他们在虚拟的堆内存指向的不是同一块内存地址
    //  cloneCurrentModel里面保存的是当前模型将要发生的变化

    // 遍历模型数据元  
    for (var key in cloneCurrentModel) {
        // 块元素的数据源
        var blockModel = cloneCurrentModel[key];
        // 实现算法
        var temp = blockModel.row;
        blockModel.row = blockModel.col;    //移动后的行 = 移动前的列
        blockModel.col = 3-temp;  //移动后的列 = 3 - 移动前的行
         
    }

    // 如果旋转之后会发生触碰，那么就不需要进行旋转了
    if (isMeet(currentX,currentY,cloneCurrentModel)) {
        return;
    }

    // 接受了这次旋转
    currentModel = cloneCurrentModel;

    locationBlocks();
}

// 控制模型只能在容器中移动
function checkBound() {
    // 定义模型可以活动的边界
    var leftBound = 0,
        rightBound = COL_COUNT,
        bottomBound = ROW_COUNT;

    // 当块元素超出了边界之后，让16宫格后退一步
    for (var key in currentModel) {
        // 块元素的数据源
        var blockModel = currentModel[key];
        // 左侧越界
        if ((blockModel.col + currentX)<leftBound) {
            currentX ++;
        }
        // 右侧越界
        if ((blockModel.col + currentX)>=rightBound) {
            currentX--;
        }
        // 底部越界
        if ((blockModel.row+currentY)>=bottomBound) {
            currentY--;
            fixedBottomModel(); // 把模型固定在底部
        }
    }

}

// 把模型固定在底部
function fixedBottomModel(){
    // 1.改变模型的样式
    // 2.让模型不可以在进行移动
    var activityModelEles = document.getElementsByClassName("activity_model");
    // for (var i =0;i<activityModelEles.length;i++) {         //JS会自动将改掉类名的元素(activity_model)删掉
    for (var i = activityModelEles.length-1; i>=0 ;i--) { 
        // 拿到每个块元素
        var activityModelEle = activityModelEles[i];
        // 更改块元素的类名
        activityModelEle.className = "activity_model1";

        // 把块元素放入变量中
        var blockModel = currentModel[i];
        fixedBlocks[(currentY + blockModel.row) + "_" + (currentX + blockModel.col)] = activityModelEle;
    }

    // 判断是否要清理
    isRemoveLine();

    // 3.创建新的模型    
    createModel();

}

// 判断模型之间的触碰问题(碰撞检测)
// X,Y 表示16宫格《将要》移动到的位置
// model 表示当前模型数据源《将要》完成的变化 
function isMeet(x,y,model) {
    // 所谓模型之间的触碰，在一个固定的位置已经存在一个被固定的块元素时，那么活动中的模型不可以再占用该位置
    // 判断触碰，就是在判断活动中的模型《将要移动到的位置》是否已经存在被固定的模型（块元素）了
    // 如果存在返回true，表示将要移动到的位置会发生触碰，否则返回false
    for (var k in model) {
        var blockModel = model[k];
        // 该位置是否已经存在块元素
        if (fixedBlocks[(y+blockModel.row)+ "_" + (x + blockModel.col)] ) {
            return true;
        }
    }
    return false;                
}

// 判断一行是否被铺满
function isRemoveLine() {
    // 在一行中，每一列元素都存在块元素，那么该行就需要被清理了
    // 遍历所有行中的所有列
    // 遍历所有行
    for (var i=0;i<ROW_COUNT;i++) {
        // 标记符，假设当前行已经被铺满了
        var flag = true;
        // 遍历当前行中所有列
        for(var j =0;j< COL_COUNT;j++){
            // 如果当前行中有一列没有数据，那么就说明当前行没有被铺满
            if (!fixedBlocks[i + "_" + j]) {
                flag = false;
                break;
            }
        }
        if (flag) {
            // 该行已经满了
            removeLine(i);
        }

    }

}

// 清理被铺满的这一行
function removeLine(line){
    // 2.删除该行中所有块元素的数据源
    // 遍历该行中所有列
    for(var i =0;i<COL_COUNT;i++){
        // 1.删除该行中所有的块元素
        document.getElementById("container").removeChild(fixedBlocks[line + "_" +i]);
        // 2.删除该行中所有块元素的数据源
        fixedBlocks[line + "_" + i] = null;
    }
    downLine(line);
}

// 让被清理行之上的块元素下落
function downLine(line) {
    //1.被清理行之上的所有块元素数据源所在行数 + 1
    //2.让块元素在容器中的位置下落
    //3.清理掉之前的块元素

    // 遍历被清理行之上的所有行
    for(var i = line-1;i>=0;i--){
        // 该行中的所有行
        for(var j =0;j<COL_COUNT;j++){
            if (!fixedBlocks[i+"_"+j]) continue;
            // 存在数据
            //1.被清理行之上的所有块元素数据源所在行数 + 1
            fixedBlocks[(i+1) + "_" + j] = fixedBlocks[i + "_"+j];
            //2.让块元素在容器中的位置下落
            fixedBlocks[(i+1) + "_" + j].style.top = (i+1)*STEP + "px";           
            //3.清理掉之前的块元素
            fixedBlocks[i +"_" + j] = null;
        }
    }
}

// 让模型自动下落
// setInterval() 方法会不停地调用函数，直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
function autoDown() {
    if (mInterval) {
        clearInterval(mInterval);
    }
    mInterval = setInterval(function(){
        move(0,1);
    },800)
}

// 判断游戏结束
function isGameOver(){
    // 当第0行存在块元素的时候，结束
    for (var i = 0;i<COL_COUNT;i++){
        if (fixedBlocks["0_"+i]) {
            return true;
        };
    }
    return false;
}

// 结束掉游戏
function GameOver(){
    // 1.停止定时器
    if (mInterval) {
        clearInterval(mInterval);
    }
    // 2.弹出对话框
    alert("祝大家新年快乐，牛年大吉！！！")

}

